package com.hcl.orm.core.spring;

import cn.hutool.core.util.StrUtil;
import com.hcl.orm.core.annotation.*;
import com.hcl.orm.core.dialect.Signature;
import com.hcl.orm.core.dialect.SqlDefinition;
import com.hcl.orm.core.dialect.SqlMetadata;
import com.hcl.orm.core.dialect.handler.AbstractDialectHandler;
import com.hcl.orm.core.exception.OrmException;

import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author hucl
 * @projectName hcl-orm
 * @description: DAO层方法缓存管理器
 * @date 2021/8/2415:51
 */
public class DaoMethodCacheManager {
    private static final Map<Integer, SqlDefinition> METHOD_CACHE;

    static {
        METHOD_CACHE = new ConcurrentHashMap<>(5000);
    }

    /**
     * 注册@DataAccess标记的类
     *
     * @param clazz
     */
    public static void register(Class<?> clazz) {
        Arrays.stream(clazz.getMethods()).forEach(m -> {
            if (m.isAnnotationPresent(Scripts.class) ||
                    m.isAnnotationPresent(Script.class)) {
                Signature signature = new Signature(m);
                Script[] scripts = m.getAnnotationsByType(Script.class);
                SqlMetadata stdSqlMetadata = null;
                List<SqlMetadata> nativeSqlMetaDatas = null;

                for (Script script : scripts) {
                    if (StrUtil.isEmpty(script.table()) && StrUtil.isEmpty(script.value())) {
                        throw new OrmException("@Script annotation table can not be empty " +
                                "when the command property is in SELECT,UPDATE,INSERT,DELETE");
                    }

                    SqlMetadata sqlMetadata = new SqlMetadata();
                    sqlMetadata.setSqlCommandType(script.command());
                    sqlMetadata.setSelective(m.isAnnotationPresent(Selective.class));
                    sqlMetadata.setTargetType(script.targetType());
                    if (!StrUtil.isEmpty(script.table())) {
                        sqlMetadata.setTableName(script.table());
                    }
                    if (!StrUtil.isEmpty(script.value())) {
                        sqlFunctionParamFormat(sqlMetadata, signature, script.value());
                    }
                    if (script.targetType() == DBType.ALL) {
                        if (stdSqlMetadata != null) {
                            throw new OrmException("repetitive STD script");
                        }
                        stdSqlMetadata = sqlMetadata;
                    } else {
                        nativeSqlMetaDatas = new ArrayList<>();
                        nativeSqlMetaDatas.add(sqlMetadata);
                    }
                }
                if (stdSqlMetadata == null) {
                    throw new OrmException("the std sql script can not be empty.");
                }
                SqlDefinition sqlDefinition = new SqlDefinition(stdSqlMetadata, nativeSqlMetaDatas, signature);
                METHOD_CACHE.putIfAbsent(m.hashCode(), sqlDefinition);
            }
        });
    }

    public static SqlDefinition gtSqlDefinition(Method method) {
        if (METHOD_CACHE.containsKey(method.hashCode())) {
            return METHOD_CACHE.get(method.hashCode());
        } else {
            throw new OrmException("Not exist the method:" + method.getName());
        }
    }

    /**
     * SQL函数格式化
     *
     * @param sqlMetadata
     * @param signature
     * @param hsSql
     */
    private static void sqlFunctionParamFormat(SqlMetadata sqlMetadata, Signature signature, String hsSql) {
        String[] paramPlaceholders = StrUtil.subBetweenAll(hsSql, "#{", "}");
        Map<Integer, Integer> paramsIndexMap = new HashMap<>();
        Integer displayIndex = 1;
        for (String param : paramPlaceholders) {
            Integer argIndex = -1;
            for (Map.Entry<Integer, String> entry : signature.getParams().entrySet()) {
                String argName = entry.getValue();
                if (argName.equals(param) || ("??" + argName).equals(param)) {
                    argIndex = entry.getKey();
                    paramsIndexMap.put(displayIndex, argIndex);
                    hsSql = hsSql.replaceFirst(argName, displayIndex.toString());
                }
                continue;
            }
            if (argIndex == -1) {
                throw new OrmException("the param name dismatched.");
            }
            displayIndex++;
        }
        sqlMetadata.setRawSql(hsSql);
        sqlMetadata.setParamsIndexMap(paramsIndexMap);
    }

    public static void refresh(AbstractDialectHandler dialectHandler, Method[] methods) {
        Arrays.stream(methods).forEach(m -> {
            SqlDefinition definition = METHOD_CACHE.get(m.hashCode());
            if (definition != null) {
                dialectHandler.handle(definition);
            }
        });
    }
}
